package xyz.wadewhy.basicstest.juc;

import lombok.extern.slf4j.Slf4j;

import static java.lang.Thread.sleep;

/**
 * @ClassName ThreadDemoV3
 * @Description sleep 和 wait
 * @Author zhongzihao
 * @Date 2022/1/4 10:19 上午
 * @Version 1.0
 **/
@Slf4j(topic = "c.Test")
public class ThreadDemoV3 {
    public static void main(String[] args) throws InterruptedException {
//        sleepDemo();
//        waitDemo();
//        falkWakeup();
        waitNotifyAll();
    }

    /**
     * 这里用final修饰是有讲究的
     * 由于这是对象锁，有可能在某段逻辑中改变了引用，锁的不是同一个对象
     */
    static final Object LOCK = new Object();
    static boolean hasCigarette = false;
    static boolean hasWine = false;

    /**
     * sleep模拟多线程
     * sleep相当于拿到锁，在屋里睡觉，睡醒继续执行逻辑，期间不会放弃锁，可以interrupte打断它
     * 现象如下，2s过后有烟进行干活
     * 11:24:02.268 [烟鬼] INFO c.Test - 没烟抽，不干活
     * 11:24:02.285 [送烟的] DEBUG c.Test - 烟到了噢！
     * 11:24:04.279 [烟鬼] INFO c.Test - 有烟没？true
     * 11:24:04.285 [烟鬼] DEBUG c.Test - 可以开始干活了
     * 11:24:04.285 [抢工作的人4] INFO c.Test - 抢工作
     * 思考：
     * 送烟的线程也用synchronized加锁会怎样，由于这里没有while循环
     * 2s后烟到了，此时hasCigarette还没改为true
     * 11:38:36.223 [烟鬼] INFO c.Test - 没烟抽，不干活
     * 11:38:38.233 [烟鬼] INFO c.Test - 有烟没？false
     * 11:38:38.237 [送烟的] DEBUG c.Test - 烟到了噢！
     * 11:38:38.237 [抢工作的人4] INFO c.Test - 抢工作
     */
    public static void sleepDemo() {
        new Thread(() -> {
            synchronized (LOCK) {
                if (!hasCigarette) {
                    log.info("没烟抽，不干活");
                    try {
                        sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.info("有烟没？{}", hasCigarette);
                if (hasCigarette) {
                    log.debug("可以开始干活了");
                } else {
                    log.debug("还没有烟");
                }
            }
        }, "烟鬼").start();
        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                synchronized (LOCK) {
                    log.info("抢工作");
                }
            }, "抢工作的人" + i).start();
        }
        new Thread(() -> {
            hasCigarette = true;
            log.debug("烟到了噢！");
        }, "送烟的").start();
//        new Thread(() -> {
//            synchronized (LOCK){
//                hasCigarette = true;
//                log.debug("烟到了噢！");
//            }
//        }, "送烟的").start();
    }

    /**
     * 对象方法的wait 相当于要当前线程去休息室休息，直到有其他线程唤醒
     * 期间会把锁放开，其他线程就能进入。
     * 现象：
     * 12:54:22.015 [烟鬼] INFO c.Test - 没烟抽，不干活
     * 12:54:22.025 [抢工作的人2] INFO c.Test - 抢工作
     * 12:54:22.025 [抢工作的人1] INFO c.Test - 抢工作
     * 12:54:22.025 [抢工作的人0] INFO c.Test - 抢工作
     * 12:54:22.025 [抢工作的人3] INFO c.Test - 抢工作
     * 12:54:22.026 [抢工作的人4] INFO c.Test - 抢工作
     * 12:54:22.031 [送烟的] DEBUG c.Test - 烟到了噢！
     * 12:54:22.032 [烟鬼] DEBUG c.Test - 有烟抽了没 true
     * 12:54:22.036 [烟鬼] INFO c.Test - 有烟抽，开始干活
     * 思考：当有另外一个线程也在休息区，那么notify唤醒是不是会唤醒错线程呢？
     */
    public static void waitDemo() {
        new Thread(() -> {
            synchronized (LOCK) {
                if (!hasCigarette) {
                    log.info("没烟抽，不干活");
                    try {
                        LOCK.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("有烟抽了没 {}", hasCigarette);
                if (hasCigarette) {
                    log.info("有烟抽，开始干活");
                }
            }
        }, "烟鬼").start();
        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                synchronized (LOCK) {
                    log.info("抢工作");
                }
            }, "抢工作的人" + i).start();
        }
        new Thread(() -> {
            synchronized (LOCK) {
                hasCigarette = true;
                log.debug("烟到了噢！");
                //这里先埋个伏笔，用notify
                LOCK.notify();
            }
        }, "送烟的").start();
    }

    /**
     * 虚假唤醒
     * notify是随机在等待队列唤醒一个，所以可以看出，唤醒的是酒鬼，但是
     * 没有送酒，烟鬼没有被唤醒，唤醒的不是我们想要的，使用notifyAll可解决
     * 现象
     * 13:22:08.667 [酒鬼] INFO c.Test - 没酒喝，不干活
     * 13:22:08.673 [烟鬼] INFO c.Test - 没烟抽，不干活
     * 13:22:09.668 [送烟的] DEBUG c.Test - 烟到了噢！
     * 13:22:09.668 [酒鬼] DEBUG c.Test - 有酒喝了没 false
     * 13:22:09.671 [酒鬼] DEBUG c.Test - 还没有酒
     */
    public static void falkWakeup() throws InterruptedException {
        new Thread(() -> {
            synchronized (LOCK) {
                if (!hasWine) {
                    log.info("没酒喝，不干活");
                    try {
                        LOCK.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("有酒喝了没 {}", hasWine);
                if (hasWine) {
                    log.info("有酒喝，开始干活");
                } else {
                    log.debug("还没有酒");
                }
            }
        }, "酒鬼").start();
        new Thread(() -> {
            synchronized (LOCK) {
                if (!hasCigarette) {
                    log.info("没烟抽，不干活");
                    try {
                        LOCK.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("有烟抽了没 {}", hasCigarette);
                if (hasCigarette) {
                    log.info("有烟抽，开始干活");
                } else {
                    log.debug("还没有烟");
                }
            }
        }, "烟鬼").start();
        sleep(1000);
        new Thread(() -> {
            synchronized (LOCK) {
                hasCigarette = true;
                log.debug("烟到了噢！");
                LOCK.notify();
            }
        }, "送烟的").start();
    }

    /**
     *
     * 13:24:33.732 [酒鬼] INFO c.Test - 没酒喝，不干活
     * 13:24:33.738 [烟鬼] INFO c.Test - 没烟抽，不干活
     * 13:24:34.741 [送烟的] DEBUG c.Test - 烟到了噢！
     * 13:24:34.741 [烟鬼] DEBUG c.Test - 有烟抽了没 true
     * 13:24:34.744 [烟鬼] INFO c.Test - 有烟抽，开始干活
     * 由于改成while，所以还会进入一次，但是后续没有notify，就一直在wait
     * 13:24:34.744 [酒鬼] INFO c.Test - 没酒喝，不干活
     * @throws InterruptedException
     */
    public static void waitNotifyAll() throws InterruptedException {
        new Thread(() -> {
            synchronized (LOCK) {
                while (!hasWine) {
                    log.info("没酒喝，不干活");
                    try {
                        LOCK.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("有酒喝了没 {}", hasWine);
                if (hasWine) {
                    log.info("有酒喝，开始干活");
                } else {
                    log.debug("还没有酒");
                }
            }
        }, "酒鬼").start();
        new Thread(() -> {
            synchronized (LOCK) {
                while (!hasCigarette) {
                    log.info("没烟抽，不干活");
                    try {
                        LOCK.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("有烟抽了没 {}", hasCigarette);
                if (hasCigarette) {
                    log.info("有烟抽，开始干活");
                } else {
                    log.debug("还没有烟");
                }
            }
        }, "烟鬼").start();
        sleep(1000);
        new Thread(() -> {
            synchronized (LOCK) {
                hasCigarette = true;
                log.debug("烟到了噢！");
                LOCK.notifyAll();
            }
        }, "送烟的").start();
    }
}
